{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Credits: https://github.com/bansalkanav/Machine_Learning_and_Deep_Learning"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# **High Speed Numerical Array Computation using NumPy Module**\n",
    "\n",
    "NumPy (**Num**erical **Py**thon) is an open source Python library that’s widely used in science and engineering. The NumPy library contains multidimensional array data structures, such as the homogeneous, N-dimensional ndarray, and a large library of functions that operate efficiently on these data structures. \n",
    "\n",
    "### **What's covered in this notebook?**\n",
    "1. Introduction to NumPy\n",
    "\t- What is an \"array\"?\n",
    "\t- Installing NumPy\n",
    "\t- How to import NumPy & Creating NumPy Array\n",
    "\t- Why NumPy when we already have lists?\n",
    "\t- NumPy vs Lists in action\n",
    "\t- NumPy Array Axis\n",
    "\t- NumPy Array Restrictions\n",
    "2. NumPy Array's Attributes/Properties\n",
    "3. Datatypes in Numpy\n",
    "4. Functions for creating NumPy Array **(Create Operation)**\n",
    "\t- Creating NumPy Array with arange()\n",
    "\t- Creating NumPy Array with ones()\n",
    "\t- Creating NumPy array with zeros()\n",
    "\t- Creating NumPy array with eye()\n",
    "\t- Creating NumPy array with linspace()\n",
    "5. Numpy Random Numbers\n",
    "\t- Why use numpy.random?\n",
    "\t- Uniform Distribution Between [0,1] - np.random.rand()\n",
    "\t- Standard Normal Distribution (Mean = 0, Std = 1) - np.random.randn()\n",
    "\t- Random Integers - np.random.randint()\n",
    "\t- Uniform Distribution in Given Range - np.random.uniform()\n",
    "\t- Normal (Gaussian) Distribution - np.random.normal()\n",
    "\t- Binomial Distribution - np.random.binomial()\n",
    "\t- Randomly Selecting Elements - np.random.choice()\n",
    "\t- shuffle() vs permutation()\n",
    "\t- Setting the Random Seed\n",
    "6. Data Accessing from NumPy Array - Indexing and Slicing **(Read Operation)**\n",
    "\t- Using Indexing\n",
    "    - Using Indexing with Boolean Arrays\n",
    "\t- Using Boolean Masking with Conditions\n",
    "\t- Using Boolean Masking with Multiple Conditions\n",
    "\t- Using Slicing\n",
    "\t\t- Copy vs View\n",
    "        - Relation to Shallow vs Deep Copy\n",
    "\t\t- How to tell if the array is a view or a copy?\n",
    "\t\t- Few more slicing examples\n",
    "7. Updating Data in Numpy Array **(Update Operation)**\n",
    "    - Updating a Single Element\n",
    "    - Updating Multiple Elements\n",
    "    - Updating an Entire Row\n",
    "    - Updating an Entire Column\n",
    "8. Deleting Elements from the NumPy Array **(Delete Operation)**\n",
    "\t- Deleting a Single Element\n",
    "\t- Deleting Multiple Elements\n",
    "\t- Deleting Row in a 2D Array\n",
    "\t- Deleting Column in a 2D Array\n",
    "    - Replacing with np.nan\n",
    "9. Insert Elements in NumPy Array\n",
    "\t- Inserting in a 1D NumPy Array\n",
    "\t- Inserting Multiple Values in 1D Array\n",
    "\t- Inserting a Row in a 2D NumPy Array\n",
    "\t- Inserting a Column in a 2D NumPy Array\n",
    "10. Changing the NumPy Array Shape\n",
    "\t- Numpy Flatten and Ravel\n",
    "\t- Flatten vs Ravel\n",
    "\t- Numpy Reshape\n",
    "\t- Transpose of NumPy Array\n",
    "11. Iterating over NumPy Array\n",
    "\t- Iterating over a 1 Dimensional Array\n",
    "\t- Iterating over a 2 Dimensional Array\n",
    "\t- Iterating using np.nditer()\n",
    "12. Operations on NumPy Array\n",
    "\t- Basic Array Operations\n",
    "\t- NumPy Mathematical Operations\n",
    "\t- NumPy Matrix Multiplication\n",
    "\t- NumPy Statistics\n",
    "\t- Get Diagonal Elements\n",
    "\t- Broadcasting\n",
    "\t- Broadcasting Between Arrays of Different Shape\n",
    "13. Miscellaneous Topics\n",
    "\t- Reverse an array\n",
    "\t- Getting Unique Items and Count\n",
    "\t- Sorting\n",
    "\t- Stacking\n",
    "\t- Concatenate\n",
    "\t- Append\n",
    "\t- Process Array Elements Conditionally using where()\n",
    "\t- argsort(), argmin() and argmax()\n",
    "14. Reading CSV into NumPy Array"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## **Introduction to NumPy**\n",
    "\n",
    "### **What is an \"array\"?**\n",
    "\n",
    "In computer programming, an array is a structure for storing and retrieving data. We often talk about an array as if it were a grid in space, with each cell storing one element of the data. For instance, if each element of the data were a number, we might visualize a \"one-dimensional\" array like a list:<br />\n",
    "<img width=\"400\" height=\"400\" src=\"images/one_d_array.png\"><br />\n",
    "\n",
    "\n",
    "A two-dimensional array would be like a table:<br />\n",
    "<img width=\"400\" height=\"400\" src=\"images/two_d_array.png\"><br />\n",
    "\n",
    "\n",
    "A three-dimensional array would be like a set of tables, perhaps stacked as though they were printed on separate pages. In NumPy, this idea is generalized to an arbitrary number of dimensions, and so the fundamental array class is called ndarray: it represents an \"N-dimensional array\".\n",
    "\n",
    "### **Installing NumPy**\n",
    "```python\n",
    "! pip install numpy\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# # Install a pip package in the current Jupyter kernel\n",
    "# ! pip install numpy"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **How to import NumPy & Creating NumPy Array**\n",
    "```python\n",
    "import numpy as np\n",
    "\n",
    "a = np.array([1, 2, 3, 4, 5, 6])\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-08-08T17:01:40.832014Z",
     "start_time": "2018-08-08T17:01:40.819538Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'numpy.ndarray'>\n",
      "Numpy Array:  [1 2 3 4]\n"
     ]
    }
   ],
   "source": [
    "# Creating a simple array in numpy\n",
    "import numpy as np\n",
    "\n",
    "arr = np.array([1, 2, 3, 4])\n",
    "\n",
    "print(type(arr))\n",
    "\n",
    "print(\"Numpy Array: \", arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Why NumPy when we already have lists?**\n",
    "Python lists are excellent, general-purpose containers. They can be “heterogeneous”, meaning that they can contain elements of a variety of types, and they are quite fast when used to perform individual operations on a handful of elements.\n",
    "\n",
    "Depending on the characteristics of the data and the types of operations that need to be performed, other containers may be more appropriate; by exploiting these characteristics, we can improve speed, reduce memory consumption, and offer a high-level syntax for performing a variety of common processing tasks. NumPy shines when there are large quantities of “homogeneous” (same-type) data to be processed on the CPU.\n",
    "\n",
    "Other reasons:\n",
    "- Most powerful numerical processing library in python. Array Oriented computing.\n",
    "- Provides extension package to python for multi dimensional array.\n",
    "- Very efficient.\n",
    "- Scientific computation."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **NumPy vs Lists in action**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-08-08T12:42:55.929589Z",
     "start_time": "2018-08-08T12:42:53.436962Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 87.2 ms, sys: 6.13 ms, total: 93.3 ms\n",
      "Wall time: 92 ms\n"
     ]
    }
   ],
   "source": [
    "%%time\n",
    "\n",
    "lst = list(range(1000000))\n",
    "\n",
    "for i in range(1000000):\n",
    "    lst[i] *= lst[i]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-08-08T12:43:21.623675Z",
     "start_time": "2018-08-08T12:43:07.072457Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 1.59 ms, sys: 2.08 ms, total: 3.67 ms\n",
      "Wall time: 2.14 ms\n"
     ]
    }
   ],
   "source": [
    "%%time\n",
    "\n",
    "arr = np.arange(1000000)\n",
    "\n",
    "arr = arr * arr"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **NumPy Array Axis**\n",
    "In NumPy, a dimension of an array is sometimes referred to as an \"axis\". This terminology may be useful to disambiguate between the dimensionality of an array and the dimensionality of the data represented by the array. \n",
    "\n",
    "For instance, the array **arr** could represent three points, each lying within a four-dimensional space, but a has only two \"axes\".\n",
    "\n",
    "In NumPy, \"axis\" helps determine the direction of operations in a multi-dimensional array."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **NumPy Array Restrictions**\n",
    "Most NumPy arrays have some restrictions. For instance:\n",
    "- All elements of the array must be of the same type of data.\n",
    "- Once created, the total size of the array can't change.\n",
    "- The shape must be \"rectangular\", not \"jagged\"; e.g., each row of a two-dimensional array must have the same number of columns.\n",
    "\n",
    "When these conditions are met, NumPy exploits these characteristics to make the array faster, more memory efficient, and more convenient to use than less restrictive data structures."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## **NumPy Array's Attributes/Properties**\n",
    "\n",
    "The important attributes of an ndarray object are:\n",
    "\n",
    "**ndarray.ndim**  \n",
    "the number of axes (dimensions) of the array.\n",
    "\n",
    "**ndarray.shape**  \n",
    "the dimensions of the array. This is a tuple of integers indicating the size of the array in each dimension. For a matrix with n rows and m columns, shape will be (n,m). The length of the shape tuple is therefore the number of axes, ndim.\n",
    "\n",
    "**ndarray.size**  \n",
    "the total number of elements of the array. This is equal to the product of the elements of shape.\n",
    "\n",
    "**ndarray.dtype**  \n",
    "an object describing the type of the elements in the array. One can create or specify dtype’s using standard Python types. Additionally NumPy provides types of its own. numpy.int32, numpy.int16, and numpy.float64 are some examples.\n",
    "\n",
    "**ndarray.itemsize**  \n",
    "the size in bytes of each element of the array. For example, an array of elements of type float64 has itemsize 8 (=64/8), while one of type complex32 has itemsize 4 (=32/8). It is equivalent to ndarray.dtype.itemsize.\n",
    "\n",
    "**ndarray.data**  \n",
    "the buffer containing the actual elements of the array. Normally, we won’t need to use this attribute because we will access the elements in an array using indexing facilities."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-06-04T18:12:38.187349Z",
     "start_time": "2018-06-04T18:12:38.178727Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array: \n",
      " [1 2 3 4]\n",
      "Shape:  (4,)\n",
      "Size:  4\n",
      "Data Type:  int64\n",
      "Item Size:  8\n",
      "Dimensionality:  1\n",
      "Data:  <memory at 0x10513a280>\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "\n",
    "arr = np.array([1, 2, 3, 4])\n",
    "\n",
    "print(\"Array: \\n\", arr)\n",
    "\n",
    "# Print shape\n",
    "print(\"Shape: \", arr.shape)\n",
    "\n",
    "# Print Size\n",
    "print(\"Size: \", arr.size)\n",
    "\n",
    "# Print datatype\n",
    "print(\"Data Type: \", arr.dtype)\n",
    "\n",
    "# Print item size in byte of each element\n",
    "print(\"Item Size: \", arr.itemsize)\n",
    "\n",
    "# Print the dimensionality of the numpy array\n",
    "print(\"Dimensionality: \", arr.ndim)\n",
    "\n",
    "# Print the databuffer of the numpy array\n",
    "print(\"Data: \", arr.data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-06-05T07:53:07.155333Z",
     "start_time": "2018-06-05T07:53:07.136784Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array: \n",
      " [[1 2 3]\n",
      " [4 5 6]]\n",
      "Shape:  (2, 3)\n",
      "Size:  6\n",
      "Data Type:  int64\n",
      "Item Size:  8\n",
      "Dimensionality:  2\n"
     ]
    }
   ],
   "source": [
    "arr = np.array([[1, 2, 3], [4, 5, 6]])\n",
    "\n",
    "print(\"Array: \\n\", arr)\n",
    "\n",
    "# Print shape\n",
    "print(\"Shape: \", arr.shape)\n",
    "\n",
    "# Print Size\n",
    "print(\"Size: \", arr.size)\n",
    "\n",
    "# Print datatype\n",
    "print(\"Data Type: \", arr.dtype)\n",
    "\n",
    "# Print item size in byte of each element\n",
    "print(\"Item Size: \", arr.itemsize)\n",
    "\n",
    "# Print the dimensionality of the numpy array\n",
    "print(\"Dimensionality: \", arr.ndim)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Shape:  (2, 3)\n",
      "Size:  6\n",
      "Data Type:  int64\n",
      "Item Size:  8\n",
      "Dimensionality:  2\n"
     ]
    }
   ],
   "source": [
    "# Let's put all the attributes in a function\n",
    "\n",
    "def array_attributes(arr):\n",
    "    # Print shape\n",
    "    print(\"Shape: \", arr.shape)\n",
    "\n",
    "    # Print Size\n",
    "    print(\"Size: \", arr.size)\n",
    "\n",
    "    # Print datatype\n",
    "    print(\"Data Type: \", arr.dtype)\n",
    "    \n",
    "    # Print item size in byte of each element\n",
    "    print(\"Item Size: \", arr.itemsize)\n",
    "    \n",
    "    # Print the dimensionality of the numpy array\n",
    "    print(\"Dimensionality: \", arr.ndim)\n",
    "\n",
    "\n",
    "array_attributes(arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## **Datatypes in Numpy**\n",
    "\n",
    "Every NumPy array has a data type that defines the kind of values it holds. Unlike Python, where integers and floats are flexible, NumPy enforces fixed-size data types for performance.\n",
    "\n",
    "```python\n",
    "# Specifying a Data Type\n",
    "arr = np.array([1, 2, 3], dtype=np.int16)  \n",
    "print(arr.dtype)\n",
    "\n",
    "# Output: int16\n",
    "```\n",
    "\n",
    "Below is a list of all data types in NumPy:\n",
    "| **Category**     | **Description**                         | **Example Data Types** |\n",
    "|------------------|---------------------------------------|----------------------|\n",
    "| **Integer**      | Whole numbers                        | `int8, int16, int32, int64` |\n",
    "| **Unsigned Int** | Non-negative whole numbers          | `uint8, uint16, uint32, uint64` |\n",
    "| **Floating-Point** | Decimal numbers                     | `float16, float32, float64, float128` |\n",
    "| **Complex Numbers** | Numbers with real + imaginary parts | `complex64, complex128` |\n",
    "| **Boolean**      | True/False                           | `bool` |\n",
    "| **String**       | Fixed-size text                      | `S` (Byte String) or `U` (Unicode String) |\n",
    "| **Object**       | Python objects (slowest)             | `object` |\n",
    "| **Datetime**     | Date & time values                   | `datetime64` |\n",
    "| **Timedelta**    | Differences between datetime64 values | `timedelta64` |\n",
    "\n",
    "---\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array: \n",
      " [2. 1. 4. 5.]\n",
      "Data Type:  float16\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "\n",
    "arr = np.array([2, 1, 4, 5], dtype='float16')\n",
    "\n",
    "print(\"Array: \\n\", arr)\n",
    "\n",
    "# Print datatype\n",
    "print(\"Data Type: \", arr.dtype)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array: \n",
      " [2 1 4 5]\n",
      "Data Type:  object\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "\n",
    "arr = np.array([2, 1, 4, 5], dtype='object')\n",
    "\n",
    "print(\"Array: \\n\", arr)\n",
    "\n",
    "# Print datatype\n",
    "print(\"Data Type: \", arr.dtype)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array: \n",
      " [b'Hi' b'Hello']\n",
      "Data Type:  |S10\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "\n",
    "# S10 means max 10 Bytes\n",
    "arr = np.array([\"Hi\", \"Hello\"], dtype='S10')\n",
    "\n",
    "print(\"Array: \\n\", arr)\n",
    "\n",
    "# Print datatype\n",
    "print(\"Data Type: \", arr.dtype)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array: \n",
      " ['Hi' 'Hello' 'Hello']\n",
      "Data Type:  <U5\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "\n",
    "# U10 means max 5 Characters\n",
    "arr = np.array([\"Hi\", \"Hello\", \"Hello World\"], dtype='U5')\n",
    "\n",
    "print(\"Array: \\n\", arr)\n",
    "\n",
    "# Print datatype\n",
    "print(\"Data Type: \", arr.dtype)\n",
    "# Observe how \"Hello World\" is trimmed to 5 chars"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## **Functions for creating NumPy Array (Create Operation)**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Creating NumPy Array with arange()**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-08-08T17:09:33.729635Z",
     "start_time": "2018-08-08T17:09:33.723652Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array: \n",
      " [0 1 2 3 4 5 6 7 8 9]\n",
      "Shape:  (10,)\n",
      "Size:  10\n",
      "Data Type:  int64\n",
      "Item Size:  8\n",
      "Dimensionality:  1\n"
     ]
    }
   ],
   "source": [
    "arr = np.arange(10)\n",
    "\n",
    "print(\"Array: \\n\", arr)\n",
    "\n",
    "# Print array attributes\n",
    "array_attributes(arr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-08-08T17:10:10.818333Z",
     "start_time": "2018-08-08T17:10:10.813348Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array: \n",
      " [1 2 3 4 5 6 7 8 9]\n",
      "Shape:  (9,)\n",
      "Size:  9\n",
      "Data Type:  int64\n",
      "Item Size:  8\n",
      "Dimensionality:  1\n"
     ]
    }
   ],
   "source": [
    "arr = np.arange(1, 10)\n",
    "\n",
    "print(\"Array: \\n\", arr)\n",
    "\n",
    "# Print array attributes\n",
    "array_attributes(arr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-08-08T17:10:18.026966Z",
     "start_time": "2018-08-08T17:10:18.021979Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array: \n",
      " [1 3 5 7 9]\n",
      "Shape:  (5,)\n",
      "Size:  5\n",
      "Data Type:  int64\n",
      "Item Size:  8\n",
      "Dimensionality:  1\n"
     ]
    }
   ],
   "source": [
    "arr = np.arange(1, 10, 2)\n",
    "\n",
    "print(\"Array: \\n\", arr)\n",
    "\n",
    "# Print array attributes\n",
    "array_attributes(arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Creating NumPy Array with ones()**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-06-04T18:05:36.080910Z",
     "start_time": "2018-06-04T18:05:36.074904Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array: \n",
      " [[1. 1. 1.]\n",
      " [1. 1. 1.]\n",
      " [1. 1. 1.]]\n",
      "Shape:  (3, 3)\n",
      "Size:  9\n",
      "Data Type:  float64\n",
      "Item Size:  8\n",
      "Dimensionality:  2\n"
     ]
    }
   ],
   "source": [
    "arr = np.ones((3, 3))\n",
    "\n",
    "print(\"Array: \\n\", arr)\n",
    "\n",
    "# Print array attributes\n",
    "array_attributes(arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Creating NumPy array with zeros()**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-06-04T18:06:03.504927Z",
     "start_time": "2018-06-04T18:06:03.497905Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array: \n",
      " [[0. 0. 0.]\n",
      " [0. 0. 0.]\n",
      " [0. 0. 0.]]\n",
      "Shape:  (3, 3)\n",
      "Size:  9\n",
      "Data Type:  float64\n",
      "Item Size:  8\n",
      "Dimensionality:  2\n"
     ]
    }
   ],
   "source": [
    "arr = np.zeros((3, 3))\n",
    "\n",
    "print(\"Array: \\n\", arr)\n",
    "\n",
    "# Print array attributes\n",
    "array_attributes(arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Creating NumPy array with eye()**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-06-04T18:07:24.258848Z",
     "start_time": "2018-06-04T18:07:24.250663Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array: \n",
      " [[1. 0. 0.]\n",
      " [0. 1. 0.]\n",
      " [0. 0. 1.]]\n",
      "Shape:  (3, 3)\n",
      "Size:  9\n",
      "Data Type:  float64\n",
      "Item Size:  8\n",
      "Dimensionality:  2\n"
     ]
    }
   ],
   "source": [
    "arr = np.eye(3)\n",
    "\n",
    "print(\"Array: \\n\", arr)\n",
    "\n",
    "# Print array attributes\n",
    "array_attributes(arr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-06-04T18:07:34.019258Z",
     "start_time": "2018-06-04T18:07:34.010705Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array: \n",
      " [[1. 0.]\n",
      " [0. 1.]\n",
      " [0. 0.]]\n",
      "Shape:  (3, 2)\n",
      "Size:  6\n",
      "Data Type:  float64\n",
      "Item Size:  8\n",
      "Dimensionality:  2\n"
     ]
    }
   ],
   "source": [
    "arr = np.eye(3, 2)\n",
    "\n",
    "print(\"Array: \\n\", arr)\n",
    "\n",
    "# Print array attributes\n",
    "array_attributes(arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Creating NumPy array with identity()**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array: \n",
      " [[1. 0. 0.]\n",
      " [0. 1. 0.]\n",
      " [0. 0. 1.]]\n",
      "Shape:  (3, 3)\n",
      "Size:  9\n",
      "Data Type:  float64\n",
      "Item Size:  8\n",
      "Dimensionality:  2\n"
     ]
    }
   ],
   "source": [
    "arr = np.identity(3)\n",
    "\n",
    "print(\"Array: \\n\", arr)\n",
    "\n",
    "# Print array attributes\n",
    "array_attributes(arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Creating NumPy array with linspace()**\n",
    "\n",
    "You can also use np.linspace() to create an array with values that are spaced linearly in a specified interval."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array: \n",
      " [ 0.   2.5  5.   7.5 10. ]\n",
      "Shape:  (5,)\n",
      "Size:  5\n",
      "Data Type:  float64\n",
      "Item Size:  8\n",
      "Dimensionality:  1\n"
     ]
    }
   ],
   "source": [
    "arr = np.linspace(0, 10, num=5)\n",
    "\n",
    "print(\"Array: \\n\", arr)\n",
    "\n",
    "# Print array attributes\n",
    "array_attributes(arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## **Numpy Random Numbers**\n",
    "\n",
    "### **Why use numpy.random?**\n",
    "- Generates random numbers efficiently.\n",
    "- Supports different distributions (uniform, normal, Poisson, etc.).\n",
    "- Allows reproducibility using seed()."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Uniform Distribution Between [0,1] - np.random.rand()**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-06-04T18:11:01.826029Z",
     "start_time": "2018-06-04T18:11:01.820497Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Numpy Array: \n",
      " [0.31991695 0.89737238 0.26526554 0.38195455 0.12093708]\n"
     ]
    }
   ],
   "source": [
    "# Randomly generate an array from uniform distribution\n",
    "import numpy as np\n",
    "\n",
    "arr = np.random.rand(5)\n",
    "\n",
    "print(\"Numpy Array: \\n\", arr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-06-05T02:20:20.786391Z",
     "start_time": "2018-06-05T02:20:20.755491Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Numpy Array generated randomly from uniform distribution: \n",
      " [[0.27781692 0.04084199]\n",
      " [0.55217944 0.34456267]\n",
      " [0.52331722 0.69660283]\n",
      " [0.43974156 0.91358612]\n",
      " [0.38234578 0.39615819]\n",
      " [0.02630561 0.14306389]\n",
      " [0.50827526 0.495339  ]\n",
      " [0.9125146  0.77144722]\n",
      " [0.4770144  0.17668009]\n",
      " [0.79498582 0.98552015]]\n"
     ]
    }
   ],
   "source": [
    "# Randomly generate an array with 10 rows and 2 columns\n",
    "arr = np.random.rand(10, 2)\n",
    "\n",
    "print(\"Numpy Array generated randomly from uniform distribution: \\n\", arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Standard Normal Distribution (Mean = 0, Std = 1) - np.random.randn()**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Numpy Array: \n",
      " [-0.48281179 -0.41351682  0.11777865  1.22917019  0.30486178]\n"
     ]
    }
   ],
   "source": [
    "# Randomly generate an array from normal distribution\n",
    "import numpy as np\n",
    "\n",
    "arr = np.random.randn(5)\n",
    "\n",
    "print(\"Numpy Array: \\n\", arr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Numpy Array generated randomly from normal distribution: \n",
      " [[ 0.33686223 -0.99234988 -1.11277054 -0.65222308]\n",
      " [ 0.32729085  1.28863512  1.93670509 -1.91065333]\n",
      " [-0.55912276  0.58877998  0.9029753  -0.33790837]\n",
      " [ 0.60990542  1.16277762 -0.23547259  1.20051543]\n",
      " [ 1.15883229 -0.06443576 -0.48072993 -1.53587956]]\n"
     ]
    }
   ],
   "source": [
    "# Randomly generate an array with 5 rows and 4 columns\n",
    "arr = np.random.randn(5, 4)\n",
    "\n",
    "print(\"Numpy Array generated randomly from normal distribution: \\n\", arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Random Integers - np.random.randint()**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3\n"
     ]
    }
   ],
   "source": [
    "# Generate one random integer between 0 to 9\n",
    "\n",
    "value = np.random.randint(10)\n",
    "\n",
    "print(value)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Numpy Array: \n",
      " [[7 7 0 8]\n",
      " [8 3 1 3]\n",
      " [6 7 1 2]\n",
      " [1 0 3 6]\n",
      " [4 8 4 7]]\n"
     ]
    }
   ],
   "source": [
    "# Randomly generate a 5*4 array containing values in the range of 0 to 9\n",
    "arr = np.random.randint(10, size = (5, 4))\n",
    "\n",
    "print(\"Numpy Array: \\n\", arr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Numpy Array: \n",
      " [[37 15 28 19 23 27 22 13 35 20]\n",
      " [13 17 36 29 22 22 11 39 15 28]\n",
      " [11 29 15 22 39 10 29 36 24 31]\n",
      " [18 12 18 17 17 27 39 26 30 11]\n",
      " [31 11 18 22 37 10 33 10 16 32]]\n"
     ]
    }
   ],
   "source": [
    "# Randomly generate a 5*10 array containing values in the range of 10 to 39\n",
    "arr = np.random.randint(10, 40, size = (5, 10))\n",
    "\n",
    "print(\"Numpy Array: \\n\", arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Uniform Distribution in Given Range - np.random.uniform()**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2.643793664247581\n"
     ]
    }
   ],
   "source": [
    "# Generate one random decimal value between 0 to 10\n",
    "value = np.random.uniform(10)\n",
    "\n",
    "print(value)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Numpy Array: \n",
      " [[6.40885343 3.38013023 8.96639526 5.71872825]\n",
      " [3.45169867 7.0322145  9.71499679 4.44476207]\n",
      " [6.05482878 5.56226578 1.16133812 9.97865901]\n",
      " [4.65630463 1.58894445 3.87100493 6.43004976]\n",
      " [4.53998508 3.92419919 6.61751209 3.39219602]]\n"
     ]
    }
   ],
   "source": [
    "# Randomly generate a 5*4 array containing values in the range of 0 to 10\n",
    "arr = np.random.uniform(10, size = (5, 4))\n",
    "\n",
    "print(\"Numpy Array: \\n\", arr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Numpy Array: \n",
      " [[29.40406526 28.02850547 26.79389778]\n",
      " [15.47593107 20.29857116 37.50365583]\n",
      " [34.04687722 10.63353158 31.1019078 ]\n",
      " [39.09935675 23.35367488 28.42039648]\n",
      " [13.5860653  11.36602914 19.59196651]]\n"
     ]
    }
   ],
   "source": [
    "# Randomly generate a 5*3 array containing values in the range of 10 to 40\n",
    "arr = np.random.uniform(10, 40, size = (5, 3))\n",
    "\n",
    "print(\"Numpy Array: \\n\", arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Normal (Gaussian) Distribution - np.random.normal()**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[63.37959333 43.57674211 35.30528073]\n",
      " [45.40322355 40.29127747 53.40264592]\n",
      " [52.13735661 52.08913683 43.00538756]]\n"
     ]
    }
   ],
   "source": [
    "mean, std = 50, 10\n",
    "\n",
    "print(np.random.normal(mean, std, size=(3, 3)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Binomial Distribution - np.random.binomial()**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[4 3 3 3 2]\n"
     ]
    }
   ],
   "source": [
    "# 10 trials, 50% probability\n",
    "n, p = 10, 0.5\n",
    "\n",
    "# 5 samples\n",
    "print(np.random.binomial(n, p, size=5))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Randomly Selecting Elements - np.random.choice()**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "20\n"
     ]
    }
   ],
   "source": [
    "arr = np.array([10, 20, 30, 40, 50])\n",
    "\n",
    "# Randomly pick one element\n",
    "print(np.random.choice(arr))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[40 30 30]\n"
     ]
    }
   ],
   "source": [
    "# Randomly pick 3 elements\n",
    "print(np.random.choice(arr, size=3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **shuffle() vs permutation()**\n",
    "\n",
    "- shuffle() modifies the array in place.\n",
    "- permutation() returns a new shuffled array."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1 2 4 5 3]\n"
     ]
    }
   ],
   "source": [
    "arr = np.array([1, 2, 3, 4, 5])\n",
    "\n",
    "# Shuffles in place\n",
    "np.random.shuffle(arr)\n",
    "\n",
    "print(arr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[3 1 2 4 5]\n"
     ]
    }
   ],
   "source": [
    "# Returns a shuffled copy\n",
    "arr2 = np.random.permutation(arr)\n",
    "\n",
    "print(arr2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Setting the Random Seed**\n",
    "\n",
    "To ensure reproducibility, we can set a seed so that the random numbers remain the same every time the code runs."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Generate Random Number: 0.3745401188473625\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "\n",
    "# Setting the seed\n",
    "np.random.seed(42)  \n",
    "\n",
    "print(\"Generate Random Number:\", np.random.rand())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## **Data Accessing from NumPy Array - Indexing and Slicing (Read Operation)**\n",
    "\n",
    "Since data in numpy array is **stored sequentially**, it is possible to access the data with the help of **Indexing** and **Slicing** operation. NumPy offers more indexing facilities than regular Python sequences. In addition to indexing by integers and slices, as we saw before, **arrays can be indexed by arrays of integers and arrays of booleans**."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Using `Indexing`**\n",
    "\n",
    "It is familiar practice in mathematics to refer to elements of a matrix by the row index first and the column index second. This happens to be true for two-dimensional arrays, but a better mental model is to think of the column index as coming last and the row index as second to last. This generalizes to arrays with any number of dimensions.\n",
    "\n",
    "**Syntax to access element in 1d array**\n",
    "```python\n",
    "# Access element in 1d array\n",
    "arr[indx]\n",
    "```\n",
    "\n",
    "**Syntax to access element in 2d array**\n",
    "```python\n",
    "# Way 1: Access element in 2d array\n",
    "arr[row_indx][col_indx]\n",
    "\n",
    "# Way 2: This is a better way\n",
    "arr[row_indx, col_indx]\n",
    "\n",
    "# Access multiple elements using above syntax\n",
    "arr[[list_of_row_indx], [list_of_col_indx]]\n",
    "```\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Numpy Array: \n",
      " [92 14 71 60 20]\n",
      "Shape:  (5,)\n"
     ]
    }
   ],
   "source": [
    "# Randomly generating 1 dimensional array\n",
    "arr = np.random.randint(100, size = (5, ))\n",
    "\n",
    "print(\"Numpy Array: \\n\", arr)\n",
    "\n",
    "print(\"Shape: \", arr.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Value at 2nd index:  71\n"
     ]
    }
   ],
   "source": [
    "# Accessing 2nd index\n",
    "print(\"Value at 2nd index: \", arr[2])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "ename": "IndexError",
     "evalue": "index 5 is out of bounds for axis 0 with size 5",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mIndexError\u001b[0m                                Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[40], line 2\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[38;5;66;03m# Accessing 5th index\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mValue at 5th index: \u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[43marr\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m5\u001b[39;49m\u001b[43m]\u001b[49m)\n",
      "\u001b[0;31mIndexError\u001b[0m: index 5 is out of bounds for axis 0 with size 5"
     ]
    }
   ],
   "source": [
    "# Accessing 5th index\n",
    "print(\"Value at 5th index: \", arr[5])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Numpy Array: \n",
      " [[82 86 74 74]\n",
      " [87 99 23  2]\n",
      " [21 52  1 87]\n",
      " [29 37  1 63]\n",
      " [59 20 32 75]]\n"
     ]
    }
   ],
   "source": [
    "# Randomly generating 2 dimensional array\n",
    "arr = np.random.randint(100, size = (5, 4))\n",
    "\n",
    "print(\"Numpy Array: \\n\", arr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-08-08T17:11:38.576439Z",
     "start_time": "2018-08-08T17:11:38.570454Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Value at 2nd index:  [21 52  1 87]\n"
     ]
    }
   ],
   "source": [
    "# Accessing 2nd index\n",
    "print(\"Value at 2nd index: \", arr[2])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-08-08T17:12:00.591703Z",
     "start_time": "2018-08-08T17:12:00.574747Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Accessing Value using Way-1:  52\n",
      "Accessing Value using Way-2:  52\n"
     ]
    }
   ],
   "source": [
    "# Accessing value at 2, 1 index\n",
    "print(\"Accessing Value using Way-1: \", arr[2][1])\n",
    "\n",
    "print(\"Accessing Value using Way-2: \", arr[2, 1]) \n",
    "# Way-2 syntax can be helpful to access multiple values"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-08-08T17:13:04.866472Z",
     "start_time": "2018-08-08T17:13:04.860478Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[52 99]\n"
     ]
    }
   ],
   "source": [
    "# Accesssing multiple values\n",
    "print(arr[[2, 1], [1, 1]])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[21 52  1 87]\n"
     ]
    }
   ],
   "source": [
    "# Access Entire Row\n",
    "\n",
    "print(arr[2])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[74 23  1  1 32]\n"
     ]
    }
   ],
   "source": [
    "# Access Entire Col\n",
    "\n",
    "# Access column at index 2\n",
    "print(arr[:, 2])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Using Indexing with Boolean Arrays**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Numpy Array: \n",
      " [57 21 88 48 90 58 41 91 59 79]\n",
      "Shape:  (10,)\n"
     ]
    }
   ],
   "source": [
    "# Randomly generating 1 dimensional array\n",
    "import numpy as np\n",
    "\n",
    "arr = np.random.randint(100, size = (10, ))\n",
    "\n",
    "print(\"Numpy Array: \\n\", arr)\n",
    "\n",
    "print(\"Shape: \", arr.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[57 58 59 79]\n"
     ]
    }
   ],
   "source": [
    "bool_arr = [True, False, False, False, False, True, False, False, True, True]\n",
    "\n",
    "print(arr[bool_arr])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Using Boolean Masking with Conditions**\n",
    "\n",
    "Boolean Masking is a technique in NumPy that allows you to filter elements of an array based on a condition. Instead of using loops, you can use boolean arrays (masks) to select specific values."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[False False  True  True  True]\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "\n",
    "arr = np.array([10, 20, 30, 40, 50])\n",
    "mask = arr > 25  # Create a boolean mask\n",
    "\n",
    "print(mask)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[30 40 50]\n"
     ]
    }
   ],
   "source": [
    "filtered_arr = arr[mask]  # Apply mask\n",
    "\n",
    "print(filtered_arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Using Boolean Masking with Multiple Conditions**\n",
    "We can combine multiple conditions using:\n",
    "- & is used instead of and\n",
    "- | is used instead of or\n",
    "- ~ is used for negation\n",
    "\n",
    "**Important Note:**  \n",
    "- There can be a confusion using \"&\" vs \"and\", and \"|\" vs \"or\".\n",
    "- If you try using Logical operators, i.e. \"or\" or \"and\", directly on a NumPy array, you'll get an error because they don't work element-wise on NumPy arrays.\n",
    "- \"|\" and \"&\" are bitwise operators that work element-wise on arrays."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[15 20]\n"
     ]
    }
   ],
   "source": [
    "arr = np.array([5, 10, 15, 20, 25, 30])\n",
    "\n",
    "mask = (arr > 10) & (arr < 25)  # Select numbers between 10 and 25\n",
    "\n",
    "print(arr[mask])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[45. 78. 56. 89.]\n"
     ]
    }
   ],
   "source": [
    "# Real-time use case by removing null values\n",
    "data = np.array([45, np.nan, 78, 56, np.nan, 89])\n",
    "\n",
    "# Remove NaN values\n",
    "clean_data = data[~np.isnan(data)]\n",
    "\n",
    "print(clean_data)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Using `Slicing`**\n",
    "\n",
    "Like the original list, Python slice notation can also be used for indexing.\n",
    "\n",
    "One major difference is that slice indexing of a list copies the elements into a new list, but slicing an array returns a view: an object that refers to the data in the original array. The original array can be mutated using the view.\n",
    "\n",
    "#### **Copy VS View**\n",
    "\n",
    "**Copy**  \n",
    "When a new array is created by duplicating the data buffer as well as the metadata, it is called a copy. Changes made to the copy do not reflect on the original array. Making a copy is **slower and memory-consuming** but sometimes necessary. A copy can be forced by using **ndarray.copy()**.\n",
    "\n",
    "**View**  \n",
    "A view is a new array object that shares the same data as the original array. Any modifications to the view will affect the original array, and vice versa. A view can be forced through the **ndarray.view()** method.\n",
    "\n",
    "Think of a view in NumPy like a window into the same data. It doesn't create a new copy; it just lets you look at the original array in a different way. This makes view **faster and efficient for memory management**.\n",
    "\n",
    "### **Relation to Shallow vs Deep Copy**\n",
    "| Concept | NumPy Term | Behavior |\n",
    "|---------|-----------|----------|\n",
    "| **Shallow Copy** | **View** | Shares memory, changes reflect in both |\n",
    "| **Deep Copy** | **Copy** | Creates new memory, changes are independent |\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-06-04T18:20:31.220056Z",
     "start_time": "2018-06-04T18:20:31.212497Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Numpy Array: \n",
      " [14 61 61 46 61 50 54 63  2 50]\n",
      "Shape:  (10,)\n"
     ]
    }
   ],
   "source": [
    "# Randomly generating 1 dimensional array\n",
    "import numpy as np\n",
    "\n",
    "arr = np.random.randint(100, size = (10, ))\n",
    "\n",
    "print(\"Numpy Array: \\n\", arr)\n",
    "\n",
    "print(\"Shape: \", arr.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array Slice Before: [61 61 46]\n",
      "Array Slice After: [   61 99999    46]\n",
      "\n",
      "Original Array: [   14    61 99999    46    61    50    54    63     2    50]\n"
     ]
    }
   ],
   "source": [
    "arr_slice = arr[1:4]\n",
    "\n",
    "print(\"Array Slice Before:\", arr_slice)\n",
    "\n",
    "# Updating the arr_slice element at index 1\n",
    "arr_slice[1] = 99999\n",
    "\n",
    "print(\"Array Slice After:\", arr_slice)\n",
    "print()\n",
    "print(\"Original Array:\", arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### **How to tell if the array is a view or a copy?**\n",
    "\n",
    "The **base** attribute of the ndarray makes it easy to tell if an array is a view or a copy. The base attribute of a view returns the original array while it returns None for a copy."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "None\n",
      "[   14    61 99999    46    61    50    54    63     2    50]\n"
     ]
    }
   ],
   "source": [
    "print(arr.base) # base attribute of copy returns None\n",
    "\n",
    "print(arr_slice.base) # base attribute of view returns the original array"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### **Few more slicing examples**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [],
   "source": [
    "# print(arr[1:4])\n",
    "\n",
    "# print(arr[0:-4])\n",
    "\n",
    "# print(arr[::2])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-08-08T17:15:57.354534Z",
     "start_time": "2018-08-08T17:15:57.348549Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Numpy Array: \n",
      " [[ 1  2  3  4]\n",
      " [ 5  6  7  8]\n",
      " [ 9 10 11 12]]\n",
      "Shape:  (3, 4)\n"
     ]
    }
   ],
   "source": [
    "arr = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])\n",
    "\n",
    "print(\"Numpy Array: \\n\", arr)\n",
    "\n",
    "print(\"Shape: \", arr.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-08-08T17:16:02.301525Z",
     "start_time": "2018-08-08T17:16:02.297494Z"
    }
   },
   "outputs": [],
   "source": [
    "# # Remember Way-2 for accessing the data in Numpy array\n",
    "# print(arr[:2, 1:3])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [],
   "source": [
    "# print(arr[1:2, :])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise: Write a program to filter the values from the array based on below mentioned conditions:  \n",
    "Either value should be divisible by 5.  \n",
    "(or) value should be an odd number and factor of 7.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Code"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## **Updating Data in Numpy Array (Update Operation)**\n",
    "\n",
    "Remember that **numpy array's are mutable**. i.e. Data stored in numpy array can be updated/changed."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Updating a Single Element**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-08-08T17:13:26.530550Z",
     "start_time": "2018-08-08T17:13:26.525565Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original Array: \n",
      " [[ 6 20 72 38]\n",
      " [17  3 88 59]\n",
      " [13  8 89 52]\n",
      " [ 1 83 91 59]\n",
      " [70 43  7 46]]\n",
      "Updated Array: \n",
      " [[   6   20   72   38]\n",
      " [  17 9999   88   59]\n",
      " [  13    8   89   52]\n",
      " [   1   83   91   59]\n",
      " [  70   43    7   46]]\n"
     ]
    }
   ],
   "source": [
    "# Updating value in the array\n",
    "import numpy as np\n",
    "\n",
    "# Randomly generating 2 dimensional array\n",
    "arr = np.random.randint(100, size = (5, 4))\n",
    "\n",
    "print(\"Original Array: \\n\", arr)\n",
    "\n",
    "# Update a single element\n",
    "arr[1, 1] = 9999\n",
    "\n",
    "print(\"Updated Array: \\n\", arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Updating Multiple Elements**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Updated Array: \n",
      " [[8888   20   72   38]\n",
      " [  17 9999   88   59]\n",
      " [  13    8   89   52]\n",
      " [   1   83   91   59]\n",
      " [  70 7777    7   46]]\n"
     ]
    }
   ],
   "source": [
    "# Update a multiple elements\n",
    "\n",
    "arr[[0, 4], [0, 1]] = [8888, 7777]\n",
    "\n",
    "print(\"Updated Array: \\n\", arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Updating an Entire Row**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "After updating row 0:\n",
      " [[ 100  200  300  400]\n",
      " [  17 9999   88   59]\n",
      " [  13    8   89   52]\n",
      " [   1   83   91   59]\n",
      " [  70 7777    7   46]]\n"
     ]
    }
   ],
   "source": [
    "# Update an entire row\n",
    "\n",
    "arr[0] = [100, 200, 300, 400]\n",
    "\n",
    "print(\"After updating row 0:\\n\", arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Updating an Entire Column**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "After updating column 2:\n",
      " [[ 100  200  999  400]\n",
      " [  86 9999  888   43]\n",
      " [  86   84  777   50]\n",
      " [  23   45  666   67]\n",
      " [  30 7777  555   55]]\n"
     ]
    }
   ],
   "source": [
    "# Update an entire column\n",
    "\n",
    "arr[:, 2] = [999, 888, 777, 666, 555]\n",
    "\n",
    "print(\"After updating column 2:\\n\", arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## **Deleting Elements from the NumPy Array (Delete Operation)**\n",
    "\n",
    "Deleting means removing elements from an array using np.delete().\n",
    "\n",
    "**Syntax**  \n",
    "```python\n",
    "np.delete(arr, indx, axis=None)\n",
    "```\n",
    "\n",
    "**Note:**  \n",
    "- axis=None for flattened array\n",
    "- axis=0 Affects Columns\n",
    "- axis=1 Affects Rows"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original Array:\n",
      " [10 20 30 40 50]\n"
     ]
    }
   ],
   "source": [
    "arr = np.array([10, 20, 30, 40, 50])\n",
    "\n",
    "print(\"Original Array:\\n\", arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Deleting a Single Element**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "After deleting index 2: [10 20 40 50]\n"
     ]
    }
   ],
   "source": [
    "# Delete element at index 2\n",
    "\n",
    "arr_new = np.delete(arr, 2, axis=None)\n",
    "\n",
    "print(\"After deleting index 2:\", arr_new)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Deleting Multiple Elements**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "After deleting indices 1 and 3: [10 30 50]\n"
     ]
    }
   ],
   "source": [
    "# Delete multiple indices\n",
    "\n",
    "arr_new = np.delete(arr, [1, 3])\n",
    "\n",
    "print(\"After deleting indices 1 and 3:\", arr_new)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Deleting Row in a 2D Array**\n",
    "\n",
    "**NumPy does not allow deleting a single element from a 2D array** while preserving its shape because arrays have fixed dimensions. However, you can delete an entire row or column."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original Array:\n",
      " [[10 20 30]\n",
      " [40 50 60]\n",
      " [70 80 90]]\n"
     ]
    }
   ],
   "source": [
    "arr2D = np.array([[10, 20, 30], \n",
    "                  [40, 50, 60], \n",
    "                  [70, 80, 90]])\n",
    "\n",
    "print(\"Original Array:\\n\", arr2D)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "After deleting row 1:\n",
      " [[10 20 30]\n",
      " [70 80 90]]\n"
     ]
    }
   ],
   "source": [
    "# Delete row 1\n",
    "arr2D_new = np.delete(arr2D, 1, axis=0)\n",
    "\n",
    "print(\"After deleting row 1:\\n\", arr2D_new)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Deleting Column in a 2D Array**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "After deleting column 2:\n",
      " [[10 20]\n",
      " [40 50]\n",
      " [70 80]]\n"
     ]
    }
   ],
   "source": [
    "# Delete column 2\n",
    "\n",
    "arr2D_new = np.delete(arr2D, 2, axis=1)\n",
    "\n",
    "print(\"After deleting column 2:\\n\", arr2D_new)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Replacing with np.nan**\n",
    "\n",
    "Since deletion isn't allowed while keeping the shape, you can replace the element with a placeholder (like np.nan or 0)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {},
   "outputs": [
    {
     "ename": "ValueError",
     "evalue": "cannot convert float NaN to integer",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mValueError\u001b[0m                                Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[60], line 2\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[38;5;66;03m# Replace the element at 1, 1 index with NaN (Not a Number)\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m \u001b[43marr2D\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m]\u001b[49m \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39mnan\n\u001b[1;32m      4\u001b[0m \u001b[38;5;28mprint\u001b[39m(arr2D)\n",
      "\u001b[0;31mValueError\u001b[0m: cannot convert float NaN to integer"
     ]
    }
   ],
   "source": [
    "# Replace the element at 1, 1 index with NaN (Not a Number)\n",
    "arr2D[1, 1] = np.nan\n",
    "\n",
    "print(arr2D)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Caution: np.nan works only with float arrays.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original Array:\n",
      " [[10. 20. 30.]\n",
      " [40. 50. 60.]\n",
      " [70. 80. 90.]]\n"
     ]
    }
   ],
   "source": [
    "arr2D = np.array([[10, 20, 30], \n",
    "                  [40, 50, 60], \n",
    "                  [70, 80, 90]], dtype=\"float32\")\n",
    "\n",
    "print(\"Original Array:\\n\", arr2D)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[10. 20. 30.]\n",
      " [40. nan 60.]\n",
      " [70. 80. 90.]]\n"
     ]
    }
   ],
   "source": [
    "# Replace the element at 1, 1 index with NaN (Not a Number)\n",
    "arr2D[1, 1] = np.nan\n",
    "\n",
    "print(arr2D)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## **Insert Elements in NumPy Array**\n",
    "\n",
    "The np.insert() function is used to insert values into a NumPy array at specified positions **without modifying the original array** (it returns a new array). It works on both 1D and multi-dimensional arrays.\n",
    "\n",
    "**Syntax**\n",
    "```python\n",
    "np.insert(arr, index, values, axis=None)\n",
    "```\n",
    "\n",
    "**Note:**  \n",
    "- axis=None for flattened array\n",
    "- axis=0 Affects Columns\n",
    "- axis=1 Affects Rows"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original Array:\n",
      " [10 20 30 40]\n"
     ]
    }
   ],
   "source": [
    "arr = np.array([10, 20, 30, 40])\n",
    "\n",
    "print(\"Original Array:\\n\", arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Inserting in a 1D NumPy Array**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "After Insertion: [10 20 25 30 40]\n"
     ]
    }
   ],
   "source": [
    "new_arr = np.insert(arr, 2, 25)  # Insert 25 at index 2\n",
    "\n",
    "print(\"After Insertion:\", new_arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Inserting Multiple Values in 1D Array**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original Array:\n",
      " [10 20 30 40]\n",
      "\n",
      "After Insertion: [10 99 20 30 88 40]\n"
     ]
    }
   ],
   "source": [
    "new_arr = np.insert(arr, [1, 3], [99, 88])\n",
    "\n",
    "print(\"Original Array:\\n\", arr)\n",
    "print()\n",
    "print(\"After Insertion:\", new_arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Inserting a Row in a 2D NumPy Array**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original Array:\n",
      " [[1 2 3]\n",
      " [4 5 6]]\n"
     ]
    }
   ],
   "source": [
    "arr2D = np.array([[1, 2, 3], \n",
    "                  [4, 5, 6]])\n",
    "\n",
    "print(\"Original Array:\\n\", arr2D)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[1 2 3]\n",
      " [7 8 9]\n",
      " [4 5 6]]\n"
     ]
    }
   ],
   "source": [
    "new_row = [7, 8, 9]\n",
    "\n",
    "# Insert at row index 1\n",
    "new_arr = np.insert(arr2D, 1, new_row, axis=0)\n",
    "\n",
    "print(new_arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Inserting a Column in a 2D NumPy Array**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[ 1 99  2  3]\n",
      " [ 4 88  5  6]]\n"
     ]
    }
   ],
   "source": [
    "new_col = [99, 88]\n",
    "\n",
    "# Insert at column index 1\n",
    "new_arr = np.insert(arr2D, 1, new_col, axis=1)\n",
    "\n",
    "print(new_arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## **Changing the NumPy Array Shape**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Numpy Flatten and Ravel**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Numpy Array: \n",
      " [[10 39 20 37 19 19 33 28 14 20]\n",
      " [35 15 19 22 31 20 15 10 37 18]\n",
      " [37 36 31 39 26 18 33 33 20 16]\n",
      " [31 19 13 16 36 16 33 26 10 15]\n",
      " [18 13 13 22 15 24 37 23 18 17]]\n",
      "Shape:  (5, 10)\n"
     ]
    }
   ],
   "source": [
    "# Randomly generate a 5*10 array containing values in the range of 10 to 39\n",
    "arr = np.random.randint(10, 40, size = (5, 10))\n",
    "\n",
    "print(\"Numpy Array: \\n\", arr)\n",
    "\n",
    "print(\"Shape: \", arr.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 70,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Flatten Array: \n",
      " [10 39 20 37 19 19 33 28 14 20 35 15 19 22 31 20 15 10 37 18 37 36 31 39\n",
      " 26 18 33 33 20 16 31 19 13 16 36 16 33 26 10 15 18 13 13 22 15 24 37 23\n",
      " 18 17]\n",
      "Shape:  (50,)\n"
     ]
    }
   ],
   "source": [
    "flatten_arr = arr.flatten()\n",
    "\n",
    "print(\"Flatten Array: \\n\", flatten_arr)\n",
    "print(\"Shape: \", flatten_arr.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Ravel Array: \n",
      " [10 39 20 37 19 19 33 28 14 20 35 15 19 22 31 20 15 10 37 18 37 36 31 39\n",
      " 26 18 33 33 20 16 31 19 13 16 36 16 33 26 10 15 18 13 13 22 15 24 37 23\n",
      " 18 17]\n",
      "Shape:  (50,)\n"
     ]
    }
   ],
   "source": [
    "ravel_arr = arr.ravel()\n",
    "\n",
    "print(\"Ravel Array: \\n\", ravel_arr)\n",
    "print(\"Shape: \", ravel_arr.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Flatten vs Ravel**\n",
    "\n",
    "Look's like both are performing fatten operation on the array data. But is there any difference between two functions?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 72,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original Array: \n",
      " [[10 39 20 37 19 19 33 28 14 20]\n",
      " [35 15 19 22 31 20 15 10 37 18]\n",
      " [37 36 31 39 26 18 33 33 20 16]\n",
      " [31 19 13 16 36 16 33 26 10 15]\n",
      " [18 13 13 22 15 24 37 23 18 17]]\n"
     ]
    }
   ],
   "source": [
    "# Updating value in the array\n",
    "print(\"Original Array: \\n\", arr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 73,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Flatten Array: \n",
      " [10 99 20 37 19 19 33 28 14 20 35 15 19 22 31 20 15 10 37 18 37 36 31 39\n",
      " 26 18 33 33 20 16 31 19 13 16 36 16 33 26 10 15 18 13 13 22 15 24 37 23\n",
      " 18 17]\n",
      "Original Array: \n",
      " [[10 39 20 37 19 19 33 28 14 20]\n",
      " [35 15 19 22 31 20 15 10 37 18]\n",
      " [37 36 31 39 26 18 33 33 20 16]\n",
      " [31 19 13 16 36 16 33 26 10 15]\n",
      " [18 13 13 22 15 24 37 23 18 17]]\n"
     ]
    }
   ],
   "source": [
    "flatten_arr[1] = 99\n",
    "\n",
    "print(\"Flatten Array: \\n\", flatten_arr)\n",
    "\n",
    "print(\"Original Array: \\n\", arr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Ravel Array: \n",
      " [10 99 20 37 19 19 33 28 14 20 35 15 19 22 31 20 15 10 37 18 37 36 31 39\n",
      " 26 18 33 33 20 16 31 19 13 16 36 16 33 26 10 15 18 13 13 22 15 24 37 23\n",
      " 18 17]\n",
      "Original Array: \n",
      " [[10 99 20 37 19 19 33 28 14 20]\n",
      " [35 15 19 22 31 20 15 10 37 18]\n",
      " [37 36 31 39 26 18 33 33 20 16]\n",
      " [31 19 13 16 36 16 33 26 10 15]\n",
      " [18 13 13 22 15 24 37 23 18 17]]\n"
     ]
    }
   ],
   "source": [
    "ravel_arr[1] = 99\n",
    "\n",
    "print(\"Ravel Array: \\n\", ravel_arr)\n",
    "\n",
    "print(\"Original Array: \\n\", arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Observation:**  \n",
    "Ravel is faster than flatten() as it does not occupy any memory. Ravel returns a view of the original array."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Numpy Reshape**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 75,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Numpy Array: \n",
      " [[14 10 12 23 29 28 28 14 25 29]\n",
      " [31 12 30 28 39 21 15 19 15 27]\n",
      " [33 38 26 24 31 35 10 26 36 37]\n",
      " [10 24 24 33 31 34 26 32 16 20]\n",
      " [10 25 18 16 14 10 22 32 36 28]]\n",
      "Shape:  (5, 10)\n"
     ]
    }
   ],
   "source": [
    "# Randomly generate a 5*10 array containing values in the range of 10 to 39\n",
    "arr = np.random.randint(10, 40, size = (5, 10))\n",
    "\n",
    "print(\"Numpy Array: \\n\", arr)\n",
    "\n",
    "print(\"Shape: \", arr.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 76,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[14 10 12 23 29]\n",
      " [28 28 14 25 29]\n",
      " [31 12 30 28 39]\n",
      " [21 15 19 15 27]\n",
      " [33 38 26 24 31]\n",
      " [35 10 26 36 37]\n",
      " [10 24 24 33 31]\n",
      " [34 26 32 16 20]\n",
      " [10 25 18 16 14]\n",
      " [10 22 32 36 28]]\n"
     ]
    }
   ],
   "source": [
    "arr_reshaped = arr.reshape(10, 5)\n",
    "\n",
    "print(arr_reshaped)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 77,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[14 10]\n",
      " [12 23]\n",
      " [29 28]\n",
      " [28 14]\n",
      " [25 29]\n",
      " [31 12]\n",
      " [30 28]\n",
      " [39 21]\n",
      " [15 19]\n",
      " [15 27]\n",
      " [33 38]\n",
      " [26 24]\n",
      " [31 35]\n",
      " [10 26]\n",
      " [36 37]\n",
      " [10 24]\n",
      " [24 33]\n",
      " [31 34]\n",
      " [26 32]\n",
      " [16 20]\n",
      " [10 25]\n",
      " [18 16]\n",
      " [14 10]\n",
      " [22 32]\n",
      " [36 28]]\n"
     ]
    }
   ],
   "source": [
    "arr_reshaped = arr.reshape(25, 2)\n",
    "\n",
    "print(arr_reshaped)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 78,
   "metadata": {},
   "outputs": [],
   "source": [
    "# arr_reshaped = arr.reshape(3, 3)\n",
    "\n",
    "# print(arr_reshaped)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 79,
   "metadata": {},
   "outputs": [],
   "source": [
    "# arr_reshaped = arr.reshape(5, 50)\n",
    "\n",
    "# print(arr_reshaped)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Transpose of NumPy Array**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 80,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-06-04T18:47:31.497789Z",
     "start_time": "2018-06-04T18:47:31.491097Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original Array: \n",
      " [[1 2 3]\n",
      " [4 5 6]]\n",
      "\n",
      "Transpose: \n",
      " [[1 4]\n",
      " [2 5]\n",
      " [3 6]]\n"
     ]
    }
   ],
   "source": [
    "# Transpose of an array\n",
    "\n",
    "x = np.array([[1, 2, 3], [4, 5, 6]])\n",
    "\n",
    "print(\"Original Array: \\n\", x)\n",
    "\n",
    "print()\n",
    "\n",
    "print(\"Transpose: \\n\", x.T)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## **Iterating over NumPy Array**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Iterating over a 1 Dimensional Array**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 81,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Numpy Array: \n",
      " [25 33 13 36 33 27 33 35 22 20]\n",
      "Shape:  (10,)\n"
     ]
    }
   ],
   "source": [
    "# Randomly generate an array containing values in the range of 10 to 39\n",
    "import numpy as np\n",
    "\n",
    "arr1d = np.random.randint(10, 40, size = (10, ))\n",
    "\n",
    "print(\"Numpy Array: \\n\", arr1d)\n",
    "\n",
    "print(\"Shape: \", arr1d.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 82,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "25 33 13 36 33 27 33 35 22 20 "
     ]
    }
   ],
   "source": [
    "# Looping over items in the array\n",
    "for item in arr1d:\n",
    "    print(item, end=\" \")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Iterating over a 2 Dimensional Array**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 83,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Numpy Array: \n",
      " [[38 19 22 19 30 29 23 17 19 36]\n",
      " [24 14 36 29 29 18 13 31 38 18]\n",
      " [18 14 33 18 35 20 24 10 27 31]\n",
      " [19 27 27 18 15 10 25 21 15 25]\n",
      " [10 17 24 38 25 24 18 12 14 39]]\n",
      "Shape:  (5, 10)\n"
     ]
    }
   ],
   "source": [
    "# Randomly generate a 5*10 array containing values in the range of 10 to 39\n",
    "arr2d = np.random.randint(10, 40, size = (5, 10))\n",
    "\n",
    "print(\"Numpy Array: \\n\", arr2d)\n",
    "\n",
    "print(\"Shape: \", arr2d.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 84,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[38 19 22 19 30 29 23 17 19 36]\n",
      "[24 14 36 29 29 18 13 31 38 18]\n",
      "[18 14 33 18 35 20 24 10 27 31]\n",
      "[19 27 27 18 15 10 25 21 15 25]\n",
      "[10 17 24 38 25 24 18 12 14 39]\n"
     ]
    }
   ],
   "source": [
    "# Looping over rows in the 2d array\n",
    "for row in arr2d:\n",
    "    print(row)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 85,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "38 19 22 19 30 29 23 17 19 36 24 14 36 29 29 18 13 31 38 18 18 14 33 18 35 20 24 10 27 31 19 27 27 18 15 10 25 21 15 25 10 17 24 38 25 24 18 12 14 39 "
     ]
    }
   ],
   "source": [
    "# Looping over each item in the 2d array\n",
    "# Note that ravel is faster and creates a view\n",
    "\n",
    "for item in arr2d.ravel():\n",
    "    print(item, end=\" \")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Iterating using `np.nditer()`**\n",
    "\n",
    "**np.nditer()** is an advanced NumPy iterator that provides efficient element-wise access to arrays.\n",
    "\n",
    "**Syntax**\n",
    "```python\n",
    "for x in np.nditer(arr, op_flags=[\"readwrite\"]):\n",
    "    x[...] = -1\n",
    "```\n",
    "\n",
    "- The op_flags=['readwrite'] allows modifying array values in-place (default is 'readonly').\n",
    "- x[...] is equivalent to x = -1, but it ensures modification happens in-place.\n",
    "- `x` here is just a temporary variable referring to the current element.\n",
    "- When we do x = -1, it simply reassigns x, but doesn't modify the original array.\n",
    "- [...] ensures modifications affect the original array.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 86,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "25 33 13 36 33 27 33 35 22 20 "
     ]
    }
   ],
   "source": [
    "for item in np.nditer(arr1d):\n",
    "    print(item, end=' ')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 87,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "38 19 22 19 30 29 23 17 19 36 24 14 36 29 29 18 13 31 38 18 18 14 33 18 35 20 24 10 27 31 19 27 27 18 15 10 25 21 15 25 10 17 24 38 25 24 18 12 14 39 "
     ]
    }
   ],
   "source": [
    "for item in np.nditer(arr2d):\n",
    "    print(item, end=' ')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 88,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original Array:  [25 33 13 36 33 27 33 35 22 20]\n"
     ]
    },
    {
     "ename": "ValueError",
     "evalue": "assignment destination is read-only",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mValueError\u001b[0m                                Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[88], line 5\u001b[0m\n\u001b[1;32m      3\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m item \u001b[38;5;129;01min\u001b[39;00m np\u001b[38;5;241m.\u001b[39mnditer(arr1d):\n\u001b[1;32m      4\u001b[0m     \u001b[38;5;28;01mif\u001b[39;00m item \u001b[38;5;241m>\u001b[39m \u001b[38;5;241m20\u001b[39m:\n\u001b[0;32m----> 5\u001b[0m         \u001b[43mitem\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m]\u001b[49m \u001b[38;5;241m=\u001b[39m (item \u001b[38;5;241m*\u001b[39m \u001b[38;5;241m0\u001b[39m)\n\u001b[1;32m      7\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mUpdated Array\u001b[39m\u001b[38;5;124m\"\u001b[39m, arr1d)\n",
      "\u001b[0;31mValueError\u001b[0m: assignment destination is read-only"
     ]
    }
   ],
   "source": [
    "print(\"Original Array: \", arr1d)\n",
    "\n",
    "for item in np.nditer(arr1d):\n",
    "    if item > 20:\n",
    "        item[...] = (item * 0)\n",
    "        \n",
    "print(\"Updated Array\", arr1d)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 89,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original Array:  [25 33 13 36 33 27 33 35 22 20]\n",
      "Updated Array [ 0  0 13  0  0  0  0  0  0 20]\n"
     ]
    }
   ],
   "source": [
    "print(\"Original Array: \", arr1d)\n",
    "\n",
    "for item in np.nditer(arr1d, op_flags=['readwrite']):\n",
    "    if item > 20:\n",
    "        item[...] = (item * 0)\n",
    "        \n",
    "print(\"Updated Array\", arr1d)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise: Write a program to generate an array with shape 5*4 at random containing positive integer. Perform an update by replacing all odd numbers with -1. (Using a Loop)**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 90,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Code"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise: Given an array [1, -10, 2, 3, 0, 6], print the array in this order [0, 6, -10, 2, 1, 3]**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 91,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Code"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## **Operations on NumPy Array**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Basic Array Operations**\n",
    "\n",
    "This section covers addition, subtraction, multiplication, division, and more.\n",
    "\n",
    "Arithmetic operators on arrays apply **elementwise**. A new array is created and filled with the result."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 92,
   "metadata": {},
   "outputs": [],
   "source": [
    "data = np.array([1, 2])\n",
    "ones = np.ones(2, dtype=int)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 93,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[2 3]\n"
     ]
    }
   ],
   "source": [
    "result = data + ones\n",
    "\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 94,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0 1]\n"
     ]
    }
   ],
   "source": [
    "result = data - ones\n",
    "\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 95,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1 4]\n"
     ]
    }
   ],
   "source": [
    "result = data * data\n",
    "\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 96,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1. 1.]\n"
     ]
    }
   ],
   "source": [
    "result = data / data\n",
    "\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **NumPy Mathematical Operations**\n",
    "\n",
    "Reference: https://numpy.org/doc/stable/reference/routines.math.html\n",
    "\n",
    "All the operations like sqrt(), exp(), sin(), cos(), add(), subtract(), etc... are applied **elementwise**."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 97,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-06-05T02:34:48.745868Z",
     "start_time": "2018-06-05T02:34:48.715111Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Square Root:  2.0\n",
      "Exponent:  2.718281828459045\n",
      "Trigonometric Sin:  0.0\n",
      "Trigonometric Cos:  1.0\n",
      "... and many more\n"
     ]
    }
   ],
   "source": [
    "print(\"Square Root: \", np.sqrt(4))\n",
    "\n",
    "print(\"Exponent: \", np.exp(1))\n",
    "\n",
    "print(\"Trigonometric Sin: \", np.sin(0))\n",
    "\n",
    "print(\"Trigonometric Cos: \", np.cos(0))\n",
    "\n",
    "print(\"... and many more\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 98,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Square Root:  [1.         1.41421356 1.73205081 2.        ]\n",
      "Exponent:  [ 2.71828183  7.3890561  20.08553692 54.59815003]\n",
      "Trigonometric Sin:  [ 0.84147098  0.90929743  0.14112001 -0.7568025 ]\n",
      "Trigonometric Cos:  [ 0.54030231 -0.41614684 -0.9899925  -0.65364362]\n"
     ]
    }
   ],
   "source": [
    "arr = np.array([1, 2, 3, 4])\n",
    "\n",
    "print(\"Square Root: \", np.sqrt(arr))\n",
    "\n",
    "print(\"Exponent: \", np.exp(arr))\n",
    "\n",
    "print(\"Trigonometric Sin: \", np.sin(arr))\n",
    "\n",
    "print(\"Trigonometric Cos: \", np.cos(arr))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 99,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-06-05T02:23:07.450828Z",
     "start_time": "2018-06-05T02:23:07.419793Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Elementwise Addition: \n",
      " [[ 6  8]\n",
      " [10 12]]\n",
      "Elementwise Subtraction: \n",
      " [[-4 -4]\n",
      " [-4 -4]]\n",
      "Elementwise Multiplication: \n",
      " [[ 5 12]\n",
      " [21 32]]\n",
      "Elementwise Division: \n",
      " [[0.2        0.33333333]\n",
      " [0.42857143 0.5       ]]\n"
     ]
    }
   ],
   "source": [
    "# ELEMENT WISE OPERATIONS\n",
    "\n",
    "x = np.array([[1,2], [3,4]])\n",
    "\n",
    "y = np.array([[5,6], [7,8]])\n",
    "\n",
    "print(\"Elementwise Addition: \\n\", np.add(x, y))\n",
    "\n",
    "print(\"Elementwise Subtraction: \\n\", np.subtract(x, y))\n",
    "\n",
    "print(\"Elementwise Multiplication: \\n\", np.multiply(x, y))\n",
    "\n",
    "print(\"Elementwise Division: \\n\", np.divide(x, y))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **NumPy Matrix Multiplication**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 100,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-06-05T02:23:08.810313Z",
     "start_time": "2018-06-05T02:23:08.794685Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Matrix Multiplication (Way-1): \n",
      " [[19 22]\n",
      " [43 50]]\n",
      "Matrix Multiplication (Way-2): \n",
      " [[19 22]\n",
      " [43 50]]\n",
      "Matrix Multiplication (Way-3): \n",
      " [[19 22]\n",
      " [43 50]]\n"
     ]
    }
   ],
   "source": [
    "# Matrix Multiplication\n",
    "import numpy as np\n",
    "\n",
    "x = np.array([[1,2], [3,4]])\n",
    "\n",
    "y = np.array([[5,6], [7,8]])\n",
    "\n",
    "print(\"Matrix Multiplication (Way-1): \\n\", np.matmul(x, y))\n",
    "\n",
    "print(\"Matrix Multiplication (Way-2): \\n\", np.dot(x, y))\n",
    "\n",
    "print(\"Matrix Multiplication (Way-3): \\n\", x @ y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **NumPy Statistics**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-06-04T18:45:38.543084Z",
     "start_time": "2018-06-04T18:45:38.534447Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array: \n",
      " [[1 2]\n",
      " [3 4]]\n",
      "Sum:  10\n",
      "Columnwise Sum:  [4 6]\n",
      "Rowwise Sum:  [3 7]\n"
     ]
    }
   ],
   "source": [
    "x = np.array([[1,2], [3,4]])\n",
    "\n",
    "print(\"Array: \\n\", x)\n",
    "\n",
    "print(\"Sum: \", np.sum(x))\n",
    "\n",
    "print(\"Columnwise Sum: \", np.sum(x, axis=0)) # Affects Columns\n",
    "\n",
    "print(\"Rowwise Sum: \", np.sum(x, axis=1)) # Affects Rows"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array: \n",
      " [[1 2 3]\n",
      " [4 5 6]]\n",
      "Minimum:  1\n",
      "Columnwise Minimum:  [1 2 3]\n",
      "Rowwise Minimum:  [1 4]\n"
     ]
    }
   ],
   "source": [
    "x = np.array([[1, 2, 3], [4, 5, 6]])\n",
    "\n",
    "print(\"Array: \\n\", x)\n",
    "\n",
    "print(\"Minimum: \", np.min(x))\n",
    "\n",
    "print(\"Columnwise Minimum: \", np.min(x, axis=0)) # Affects Columns\n",
    "\n",
    "print(\"Rowwise Minimum: \", np.min(x, axis=1)) # Affects Rows"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-06-04T18:50:06.381768Z",
     "start_time": "2018-06-04T18:50:06.372287Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array: \n",
      " [160 180 146 162 184 180]\n",
      "Minimum:  146\n",
      "Maximum:  184\n",
      "Mean:  168.66666666666666\n",
      "Median:  171.0\n",
      "Variance:  187.55555555555557\n",
      "Std Dev:  13.695092389449425\n"
     ]
    }
   ],
   "source": [
    "x = np.array([160, 180, 146, 162, 184, 180])\n",
    "\n",
    "print(\"Array: \\n\", x)\n",
    "\n",
    "print(\"Minimum: \", np.min(x))\n",
    "\n",
    "print(\"Maximum: \", np.max(x))\n",
    "\n",
    "print(\"Mean: \", np.mean(x))\n",
    "\n",
    "print(\"Median: \", np.median(x))\n",
    "\n",
    "print(\"Variance: \", np.var(x))\n",
    "\n",
    "print(\"Std Dev: \", np.std(x))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2018-06-04T18:46:59.444809Z",
     "start_time": "2018-06-04T18:46:59.440033Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array: \n",
      " [[1 2 3]\n",
      " [4 5 6]]\n",
      "Minimum:  1\n",
      "Maximum:  6\n",
      "Mean:  3.5\n",
      "Median:  3.5\n",
      "Variance:  2.9166666666666665\n",
      "Std Dev:  1.707825127659933\n"
     ]
    }
   ],
   "source": [
    "x = np.array([[1, 2, 3], [4, 5, 6]])\n",
    "\n",
    "print(\"Array: \\n\", x)\n",
    "\n",
    "print(\"Minimum: \", np.min(x))\n",
    "\n",
    "print(\"Maximum: \", np.max(x))\n",
    "\n",
    "print(\"Mean: \", np.mean(x))\n",
    "\n",
    "print(\"Median: \", np.median(x))\n",
    "\n",
    "print(\"Variance: \", np.var(x))\n",
    "\n",
    "print(\"Std Dev: \", np.std(x))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[1.        , 0.88546942],\n",
       "       [0.88546942, 1.        ]])"
      ]
     },
     "execution_count": 67,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "heights = np.array([160, 180, 146, 162, 184, 180])\n",
    "\n",
    "weights = np.array([50, 78, 45, 51, 80, 60])\n",
    "\n",
    "np.corrcoef(heights, weights)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Get Diagonal Elements**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original Array: \n",
      " [[1 2 3]\n",
      " [4 5 6]]\n",
      "Diagonal:  [1 5]\n"
     ]
    }
   ],
   "source": [
    "# Diagonal elements\n",
    "\n",
    "x = np.array([[1, 2, 3], [4, 5, 6]])\n",
    "\n",
    "print(\"Original Array: \\n\", x)\n",
    "\n",
    "print(\"Diagonal: \", np.diag(x))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Broadcasting**\n",
    "\n",
    "There are times when you might want to carry out an operation between an array and a single number (also called an operation between a vector and a scalar) or between arrays of two different sizes. For example, your array (we’ll call it \"data\") might contain information about distance in miles but you want to convert the information to kilometers. You can perform this operation with:\n",
    "\n",
    "```python\n",
    "dist_in_miles = np.array([1.0, 2.0])\n",
    "convert_to_km = dist_in_miles * 1.6\n",
    "```\n",
    "<img width=\"500\" height=\"200\" src=\"images/broadcasting.png\"><br />"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original Array: \n",
      " [[1 2 3]\n",
      " [4 5 6]]\n"
     ]
    }
   ],
   "source": [
    "# We can apply any python operator on Numpy Array.\n",
    "# It will perform the operation on each element of a numpy array.\n",
    "\n",
    "x = np.array([[1, 2, 3], [4, 5, 6]])\n",
    "\n",
    "print(\"Original Array: \\n\", x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 70,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[ 6  7  8]\n",
      " [ 9 10 11]]\n"
     ]
    }
   ],
   "source": [
    "print(x + 5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[1 0 1]\n",
      " [0 1 0]]\n"
     ]
    }
   ],
   "source": [
    "print(x % 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 72,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[False False  True]\n",
      " [ True  True  True]]\n"
     ]
    }
   ],
   "source": [
    "print(x >= 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 73,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[False  True False]\n",
      " [ True False  True]]\n"
     ]
    }
   ],
   "source": [
    "print(x % 2 == 0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise: Write a program to generate an array with shape 5*4 at random containing positive integer. Perform an update by replacing all odd numbers with -1. (Without using a Loop)**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 112,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Code"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Broadcasting Between Arrays of Different Shape**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* Start matching the dimensions backward (Right to Left)\n",
    "    - Compatible - If same number appears or if one of them is 1\n",
    "    - Incompatible - Otherwise"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 114,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 115,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[2 4 6]\n",
      " [5 7 9]]\n"
     ]
    }
   ],
   "source": [
    "arr_1 = np.array([[1, 2, 3], [4, 5, 6]])\n",
    "\n",
    "arr_2 = np.array([1, 2, 3])\n",
    "\n",
    "print(arr_1 + arr_2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 116,
   "metadata": {},
   "outputs": [],
   "source": [
    "# # Run this program\n",
    "\n",
    "# arr_1 = np.array([[1, 2, 3], [4, 5, 6]])\n",
    "\n",
    "# arr_2 = np.array([[1], [2]])\n",
    "\n",
    "# print(arr_1 + arr_2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 117,
   "metadata": {},
   "outputs": [],
   "source": [
    "# # Run this program\n",
    "\n",
    "# arr_1 = np.array([[1, 2, 3], [4, 5, 6]])\n",
    "\n",
    "# arr_2 = np.array([1])\n",
    "\n",
    "# print(arr_1 + arr_2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 118,
   "metadata": {},
   "outputs": [],
   "source": [
    "# # Run this program\n",
    "\n",
    "# arr_1 = np.array([1, 2, 3, 4, 5])\n",
    "\n",
    "# arr_2 = np.array([1, 2, 3, 4])\n",
    "\n",
    "# print(arr_1 + arr_2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 119,
   "metadata": {},
   "outputs": [],
   "source": [
    "# # Run this program\n",
    "\n",
    "# arr_1 = np.array([[1], [2], [3], [4], [5]])\n",
    "\n",
    "# arr_2 = np.array([1, 2, 3, 4])\n",
    "\n",
    "# print(arr_1 + arr_2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## **Miscellaneous Topics**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Reverse an array**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 120,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Reversed Array:\n",
      " [8 7 6 5 4 3 2 1]\n"
     ]
    }
   ],
   "source": [
    "arr = np.array([1, 2, 3, 4, 5, 6, 7, 8])\n",
    "\n",
    "reversed_arr = np.flip(arr)\n",
    "\n",
    "print('Reversed Array:\\n', reversed_arr)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 121,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Reversed Array:\n",
      " [[12 11 10  9]\n",
      " [ 8  7  6  5]\n",
      " [ 4  3  2  1]]\n"
     ]
    }
   ],
   "source": [
    "arr_2d = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])\n",
    "\n",
    "reversed_arr = np.flip(arr_2d)\n",
    "\n",
    "print('Reversed Array:\\n', reversed_arr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 122,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Reversed Array:\n",
      " [[ 9 10 11 12]\n",
      " [ 5  6  7  8]\n",
      " [ 1  2  3  4]]\n"
     ]
    }
   ],
   "source": [
    "# Reverse only the rows\n",
    "arr_2d = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])\n",
    "\n",
    "reversed_arr_rows = np.flip(arr_2d, axis=0) # Affects Columns\n",
    "\n",
    "print('Reversed Array:\\n', reversed_arr_rows)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Getting Unique Items and Count**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 123,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[ 1  2  3  4]\n",
      " [ 5  6  7  8]\n",
      " [ 1  2  3  4]\n",
      " [ 9 10 11 12]]\n"
     ]
    }
   ],
   "source": [
    "a_2d = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [1, 2, 3, 4], [9, 10, 11, 12]])\n",
    "\n",
    "print(a_2d)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 124,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Unique Values:\n",
      " [ 1  2  3  4  5  6  7  8  9 10 11 12]\n"
     ]
    }
   ],
   "source": [
    "unique_values = np.unique(a_2d)\n",
    "\n",
    "print(\"Unique Values:\\n\", unique_values)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 125,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Unique Values:\n",
      " [ 1  2  3  4  5  6  7  8  9 10 11 12]\n",
      "\n",
      "Indices: [ 0  1  2  3  4  5  6  7 12 13 14 15]\n"
     ]
    }
   ],
   "source": [
    "unique_values, indices_list = np.unique(a_2d, return_index=True)\n",
    "\n",
    "print(\"Unique Values:\\n\", unique_values)\n",
    "print()\n",
    "print(\"Indices:\", indices_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Note:** If the axis argument isn’t passed, your 2D array will be flattened."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 126,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Unique Values:\n",
      " [ 1  2  3  4  5  6  7  8  9 10 11 12]\n",
      "\n",
      "Indices: [ 0  1  2  3  4  5  6  7 12 13 14 15]\n",
      "\n",
      "Count: [2 2 2 2 1 1 1 1 1 1 1 1]\n"
     ]
    }
   ],
   "source": [
    "unique_values, occurrence_count = np.unique(a_2d, return_counts=True)\n",
    "\n",
    "print(\"Unique Values:\\n\", unique_values)\n",
    "print()\n",
    "print(\"Indices:\", indices_list)\n",
    "print()\n",
    "print(\"Count:\", occurrence_count)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 127,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Unique Values:\n",
      " [[ 1  2  3  4]\n",
      " [ 5  6  7  8]\n",
      " [ 9 10 11 12]]\n",
      "\n",
      "Indices: [0 1 3]\n",
      "\n",
      "Count: [2 1 1]\n"
     ]
    }
   ],
   "source": [
    "unique_rows, indices_list, occurrence_count = np.unique(a_2d, \n",
    "                                                   axis=0, \n",
    "                                                   return_counts=True, \n",
    "                                                   return_index=True) # Affects Columns\n",
    "\n",
    "print(\"Unique Values:\\n\", unique_rows)\n",
    "print()\n",
    "print(\"Indices:\", indices_list)\n",
    "print()\n",
    "print(\"Count:\", occurrence_count)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Sorting**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 128,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[76 62 69 63 51 75 75 71 53 93]\n",
      " [96 92 81 72 54 52 99 94 96 87]\n",
      " [72 84 52 98 50 72 79 61 64 59]\n",
      " [86 55 70 83 58 75 93 75 63 57]\n",
      " [88 56 98 85 84 53 83 51 84 99]]\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "\n",
    "arr = np.random.randint(50, 100, size = (5, 10))\n",
    "\n",
    "print(arr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 129,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[51, 53, 62, 63, 69, 71, 75, 75, 76, 93],\n",
       "       [52, 54, 72, 81, 87, 92, 94, 96, 96, 99],\n",
       "       [50, 52, 59, 61, 64, 72, 72, 79, 84, 98],\n",
       "       [55, 57, 58, 63, 70, 75, 75, 83, 86, 93],\n",
       "       [51, 53, 56, 83, 84, 84, 85, 88, 98, 99]])"
      ]
     },
     "execution_count": 129,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.sort(arr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 130,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[72, 55, 52, 63, 50, 52, 75, 51, 53, 57],\n",
       "       [76, 56, 69, 72, 51, 53, 79, 61, 63, 59],\n",
       "       [86, 62, 70, 83, 54, 72, 83, 71, 64, 87],\n",
       "       [88, 84, 81, 85, 58, 75, 93, 75, 84, 93],\n",
       "       [96, 92, 98, 98, 84, 75, 99, 94, 96, 99]])"
      ]
     },
     "execution_count": 130,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Column Wise Sorting\n",
    "\n",
    "np.sort(arr, axis = 0) # Affects Columns"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 131,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[51, 53, 62, 63, 69, 71, 75, 75, 76, 93],\n",
       "       [52, 54, 72, 81, 87, 92, 94, 96, 96, 99],\n",
       "       [50, 52, 59, 61, 64, 72, 72, 79, 84, 98],\n",
       "       [55, 57, 58, 63, 70, 75, 75, 83, 86, 93],\n",
       "       [51, 53, 56, 83, 84, 84, 85, 88, 98, 99]])"
      ]
     },
     "execution_count": 131,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Row Wise Sorting\n",
    "\n",
    "np.sort(arr, axis = 1) # Affects Rows"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Stacking**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 132,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[ 5  6  7  8  9]\n",
      " [10 11 12 13 14]]\n"
     ]
    }
   ],
   "source": [
    "arr_1 = np.arange(5,15).reshape(2,5)\n",
    "\n",
    "print(arr_1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 133,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[25 26 27 28 29]\n",
      " [30 31 32 33 34]]\n"
     ]
    }
   ],
   "source": [
    "arr_2 = np.arange(25,35).reshape(2,5)\n",
    "print(arr_2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 134,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 5,  6,  7,  8,  9],\n",
       "       [10, 11, 12, 13, 14],\n",
       "       [25, 26, 27, 28, 29],\n",
       "       [30, 31, 32, 33, 34]])"
      ]
     },
     "execution_count": 134,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.vstack([arr_1, arr_2])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 135,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 5,  6,  7,  8,  9, 25, 26, 27, 28, 29],\n",
       "       [10, 11, 12, 13, 14, 30, 31, 32, 33, 34]])"
      ]
     },
     "execution_count": 135,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.hstack([arr_1, arr_2])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Concatenate**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 136,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 5,  6,  7,  8,  9],\n",
       "       [10, 11, 12, 13, 14],\n",
       "       [25, 26, 27, 28, 29],\n",
       "       [30, 31, 32, 33, 34]])"
      ]
     },
     "execution_count": 136,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.concatenate([arr_1, arr_2], axis = 0) # concatinating - vertical stacking"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 137,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 5,  6,  7,  8,  9, 25, 26, 27, 28, 29],\n",
       "       [10, 11, 12, 13, 14, 30, 31, 32, 33, 34]])"
      ]
     },
     "execution_count": 137,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.concatenate([arr_1, arr_2], axis = 1) # concatinating - horizontal stacking"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Append**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 138,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 5,  6,  7,  8,  9],\n",
       "       [10, 11, 12, 13, 14],\n",
       "       [25, 26, 27, 28, 29],\n",
       "       [30, 31, 32, 33, 34]])"
      ]
     },
     "execution_count": 138,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.append(arr_1, arr_2, axis = 0) # Appending - vertical stacking"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 139,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 5,  6,  7,  8,  9, 25, 26, 27, 28, 29],\n",
       "       [10, 11, 12, 13, 14, 30, 31, 32, 33, 34]])"
      ]
     },
     "execution_count": 139,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.append(arr_1, arr_2, axis = 1) # Appending - horizontal stacking"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Process Array Elements Conditionally using where()**\n",
    "\n",
    "Understanding np.where() Syntax \n",
    "```python\n",
    "numpy.where(  \n",
    "  condition,   # Where True, yield x, otherwise y  \n",
    "  [x, y, ]     # Values to choose from  \n",
    ")  \n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 140,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[50 51 52 53 54 55 56 57 58 59]\n",
      " [60 61 62 63 64 65 66 67 68 69]\n",
      " [70 71 72 73 74 75 76 77 78 79]\n",
      " [80 81 82 83 84 85 86 87 88 89]\n",
      " [90 91 92 93 94 95 96 97 98 99]]\n"
     ]
    }
   ],
   "source": [
    "arr = np.arange(50, 100).reshape(5, 10)\n",
    "\n",
    "print(arr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 141,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],\n",
       "       [1, 1, 1, 1, 1, 0, 0, 0, 0, 0],\n",
       "       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n",
       "       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n",
       "       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])"
      ]
     },
     "execution_count": 141,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.where(arr > 64, 0, 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 142,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[50. , 51. , 52. , 53. , 54. , 55. , 56. , 57. , 58. , 59. ],\n",
       "       [60. , 61. , 62. , 63. , 64. ,  6.5,  6.6,  6.7,  6.8,  6.9],\n",
       "       [ 7. ,  7.1,  7.2,  7.3,  7.4,  7.5,  7.6,  7.7,  7.8,  7.9],\n",
       "       [ 8. ,  8.1,  8.2,  8.3,  8.4,  8.5,  8.6,  8.7,  8.8,  8.9],\n",
       "       [ 9. ,  9.1,  9.2,  9.3,  9.4,  9.5,  9.6,  9.7,  9.8,  9.9]])"
      ]
     },
     "execution_count": 142,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.where(arr > 64, arr/10, arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **argsort(), argmin() and argmax()**\n",
    "\n",
    "In NumPy, functions like argsort(), argmin(), and argmax() are used to get the indices of elements rather than their values. These functions are extremely useful when you need to locate specific elements in an array.\n",
    "\n",
    "- np.argsort() - Returns Indices of Sorted Elements\n",
    "- np.argmin() - Returns Index of Minimum Element\n",
    "- np.argmax() - Returns Index of Maximum Element"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 143,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "arr = np.array([10, -5, 6, -1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 144,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Index of minimum element: 1\n"
     ]
    }
   ],
   "source": [
    "print(\"Index of minimum element:\", arr.argmin())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 145,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Index of maximum element: 0\n"
     ]
    }
   ],
   "source": [
    "print(\"Index of maximum element:\", arr.argmax())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 146,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Indexes: [1 3 2 0]\n",
      "Sorted Array: [-5 -1  6 10]\n"
     ]
    }
   ],
   "source": [
    "print(\"Indexes:\", arr.argsort()) # Returns indexes\n",
    "\n",
    "print(\"Sorted Array:\", arr[ arr.argsort() ])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## **Reading CSV into NumPy Array**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 147,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[['EST' 'Temperature' 'DewPoint' 'Humidity' 'Sea Level PressureIn'\n",
      "  'VisibilityMiles' 'WindSpeedMPH' 'PrecipitationIn' 'CloudCover'\n",
      "  'Events' 'WindDirDegrees']\n",
      " ['1/1/2016' '38' '23' '52' '30.03' '10' '8' '0' '5' '' '281']\n",
      " ['1/2/2016' '36' '18' '46' '30.02' '10' '7' '0' '3' '' '275']\n",
      " ['1/3/2016' '40' '21' '47' '29.86' '10' '8' '0' '1' '' '277']\n",
      " ['1/4/2016' '25' '9' '44' '30.05' '10' '9' '0' '3' '' '345']\n",
      " ['1/5/2016' '20' '-3' '41' '30.57' '10' '5' '0' '0' '' '333']\n",
      " ['1/6/2016' '33' '4' '35' '30.5' '10' '4' '0' '0' '' '259']\n",
      " ['1/7/2016' '39' '11' '33' '30.28' '10' '2' '0' '3' '' '293']\n",
      " ['1/8/2016' '39' '29' '64' '30.2' '10' '4' '0' '8' '' '79']\n",
      " ['1/9/2016' '44' '38' '77' '30.16' '9' '8' 'T' '8' 'Rain' '76']\n",
      " ['1/10/2016' '50' '46' '71' '29.59' '4' '' '1.8' '7' 'Rain' '109']\n",
      " ['1/11/2016' '33' '8' '37' '29.92' '10' '' '0' '1' '' '289']\n",
      " ['1/12/2016' '35' '15' '53' '29.85' '10' '6' 'T' '4' '' '235']\n",
      " ['1/13/2016' '26' '4' '42' '29.94' '10' '10' '0' '0' '' '284']\n",
      " ['1/14/2016' '30' '12' '47' '29.95' '10' '5' 'T' '7' '' '266']\n",
      " ['1/15/2016' '43' '31' '62' '29.82' '9' '5' 'T' '2' '' '101']\n",
      " ['1/16/2016' '47' '37' '70' '29.52' '8' '7' '0.24' '7' 'Rain' '340']\n",
      " ['1/17/2016' '36' '23' '66' '29.78' '8' '6' '0.05' '6' 'Fog-Snow' '345']\n",
      " ['1/18/2016' '25' '6' '53' '29.83' '9' '12' 'T' '2' 'Snow' '293']\n",
      " ['1/19/2016' '22' '3' '42' '30.03' '10' '11' '0' '1' '' '293']\n",
      " ['1/20/2016' '32' '15' '49' '30.13' '10' '6' '0' '2' '' '302']\n",
      " ['1/21/2016' '31' '11' '45' '30.15' '10' '6' '0' '1' '' '312']\n",
      " ['1/22/2016' '26' '6' '41' '30.21' '9' '' '0.01' '3' 'Snow' '34']\n",
      " ['1/23/2016' '26' '21' '78' '29.77' '1' '16' '2.31' '8' 'Fog-Snow' '42']\n",
      " ['1/24/2016' '28' '11' '53' '29.92' '8' '6' 'T' '3' 'Snow' '327']\n",
      " ['1/25/2016' '34' '18' '54' '30.25' '10' '3' '0' '2' '' '286']\n",
      " ['1/26/2016' '43' '29' '56' '30.03' '10' '7' '0' '2' '' '244']\n",
      " ['1/27/2016' '41' '22' '45' '30.03' '10' '7' 'T' '3' 'Rain' '311']\n",
      " ['1/28/2016' '37' '20' '51' '29.9' '10' '5' '0' '1' '' '234']\n",
      " ['1/29/2016' '36' '21' '50' '29.58' '10' '8' '0' '4' '' '298']\n",
      " ['1/30/2016' '34' '16' '46' '30.01' '10' '7' '0' '0' '' '257']\n",
      " ['1/31/2016' '46' '28' '52' '29.9' '10' '5' '0' '0' '' '241']]\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    " \n",
    "# using loadtxt()\n",
    "arr = np.loadtxt(\"data/nyc_weather.csv\", delimiter=\",\", dtype='str')\n",
    "print(arr)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.6"
  },
  "toc": {
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
